package com.ihealthway.all.groupbuy.modules.wechat;

import com.ihealthway.all.groupbuy.services.MPSettingService;
import com.ihealthway.all.task.ExecutorThreadPool;
import com.ihealthway.all.groupbuy.services.GoodsService;
import com.ihealthway.all.groupbuy.services.OrderInfoService;
import com.ihealthway.all.utils.Ajax;
import com.ihealthway.all.utils.AjaxReturn;
import com.ihealthway.all.utils.Sha1Util;
import com.ihealthway.all.utils.Utils;
import com.ihealthway.all.groupbuy.vo.Goods;
import com.ihealthway.all.groupbuy.vo.MPSetting;
import com.ihealthway.all.groupbuy.vo.OrderInfo;
import com.ihealthway.all.groupbuy.wechat.GroupBuyingWechatHandler;
import com.ihealthway.all.groupbuy.wechat.PayRequestHandler;
import io.github.elkan1788.mpsdk4j.api.WechatAPIImpl;
import io.github.elkan1788.mpsdk4j.mvc.WechatWebSupport;
import io.github.elkan1788.mpsdk4j.vo.MPAccount;
import io.github.elkan1788.mpsdk4j.vo.api.WebOauth2Result;
import org.nutz.http.Request;
import org.nutz.http.Response;
import org.nutz.http.Sender;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.Xmls;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * Wechat Main Module
 *
 * @author 凡梦星尘(elkan1788@gmail.com)
 */
@IocBean
@At("/wechat")
public class WechatMPModule extends WechatWebSupport {

    private static final Log logger = Logs.get();

    public static final Map<String, WebOauth2Result> WEB_AUTH_RESULT =  new HashMap<>();

    @Inject
    private PropertiesProxy conf;

    @Inject
    private MPAccount mpAct;

    @Inject
    private WechatAPIImpl wechatAPI;

    @Inject
    private GroupBuyingWechatHandler gbWechatHandler;

    @Inject
    private MPSettingService mpSetService;

    @Inject
    private OrderInfoService orderInfoService;

    @Inject
    private GoodsService goodsService;

    private String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    private String queryOrderURL = "https://api.mch.weixin.qq.com/pay/orderquery";

    @Override
    public void init() {
        _wk.setMpAct(mpAct);
        _wk.setWechatHandler(this.gbWechatHandler);
    }

    @At
    public void inter(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        this.interact(req, resp);
    }

    @At
    @Ok("proxy:${obj}")
    public String oauth2CallBack(@Param("..")NutMap nutMap, @Param("code") String code, @Param("state") String state, HttpServletRequest req) {
        String redirectURL = "/front/shop/checkUserStatus.do?openId=%s&re=%s";
        logger.info(nutMap);
        logger.infof("Get call back code: %s, state: %s", code, state);
        if (!WEB_AUTH_RESULT.containsKey(code)) {
            WebOauth2Result webOauth2Result = wechatAPI.getWebOauth2Result(code);
            WEB_AUTH_RESULT.put(code, webOauth2Result);
            redirectURL = String.format(redirectURL, webOauth2Result.getOpenId(), state);
        }

        logger.infof("Wechat call back url: %s", redirectURL);
        return redirectURL;
    }

    @At
    @Ok("beetl:front/wechat_pay.jsp")
    public void prepareOrderPay(@Param("orderNo")String orderNo, @Param("openId") String openId,
                              @Param("goodsId") Integer goodsId,
                              HttpServletRequest req, HttpServletResponse resp) {
        logger.debugf("prepareOrderPay --> OrderNo: %s, openId: %s, goodsId: %s", orderNo, openId, goodsId);
        NutMap result = new NutMap("result", "FAIL");
        result.put("orderId", orderNo);

        // Safe check goods and get price
        Goods goods = goodsService.getGoodsPrice(goodsId);
        if (goods != null) {

            result.put("goodsName", goods.getGoodsName());
            result.put("goodsPrice", goods.getPrice());

            String userIp = Lang.getIP(req);
            MPSetting mpSet = mpSetService.get();
            int nonceStr = Utils.buildRandom(10);
            SortedMap<String, Object> prePayIdSignParams = new TreeMap<>();
            prePayIdSignParams.put("appid", mpAct.getAppId());
            prePayIdSignParams.put("mch_id", mpSet.getMchId());
            prePayIdSignParams.put("nonce_str", nonceStr);
            prePayIdSignParams.put("body", goods.getGoodsName());
            prePayIdSignParams.put("detail", goods.getGoodsName());
            prePayIdSignParams.put("attach", orderNo);
            prePayIdSignParams.put("out_trade_no", orderNo);
            prePayIdSignParams.put("device_info", "WEB");

            // Will pay 0.01 CNY in test environment
            if (conf.getBoolean("wx_pay_test_env")) {
                prePayIdSignParams.put("total_fee", "1");
            } else {
                prePayIdSignParams.put("total_fee", goods.getPrice());
            }
            prePayIdSignParams.put("spbill_create_ip", userIp);
            String notifyUrl = req.getScheme() + "://" + conf.get("server_domain") + "/wechat/notifyOrderPay.do";
            prePayIdSignParams.put("notify_url", notifyUrl);

            prePayIdSignParams.put("trade_type", "JSAPI");
            prePayIdSignParams.put("openid", openId);

            logger.infof("PrePayParams: %s", prePayIdSignParams);
            PayRequestHandler reqHandler = new PayRequestHandler(req, resp);
            reqHandler.init(mpAct.getAppId(), mpAct.getAppSecret(), mpSet.getMchKey());

            prePayIdSignParams.put("sign", reqHandler.createSign(prePayIdSignParams));
            Request createOrderReq = Request.create(createOrderURL, Request.METHOD.POST);
            String data = Xmls.mapToXml(prePayIdSignParams);
            logger.debugf("Prepare Pay Parameters: %s", data);
            createOrderReq.setData(data);
            Response createOrderResp = Sender.create(createOrderReq).send();


            if (createOrderResp.isOK()) {
                String createOrderRsStr = createOrderResp.getContent("UTF-8");
                logger.infof("PrePay Response: %s", createOrderRsStr);
                NutMap createOrderRs = Xmls.xmlToMap(createOrderRsStr);
                logger.debugf("PrePay Response: %s", createOrderRs);
                String resultCode = createOrderRs.getString("result_code");
                if (Strings.equals("FAIL", resultCode)) {
                    result.put("errorCode", createOrderRs.getString("error_code"));
                    result.put("errorDes", createOrderRs.getString("err_code_des"));
                } else {
                    result.put("result", "OK");
                    String prePayId = createOrderRs.getString("prepay_id");
                    SortedMap<String, Object> paySignParams = new TreeMap<>();

                    String packages = "prepay_id=" + prePayId;
                    String timestamp = Sha1Util.getTimeStamp();
                    paySignParams.put("appId", mpAct.getAppId());
                    paySignParams.put("timeStamp", timestamp);
                    paySignParams.put("nonceStr", nonceStr);
                    paySignParams.put("package", packages);
                    paySignParams.put("signType", "MD5");
                    String paySign = reqHandler.createSign(paySignParams);

                    result.put("appId", mpAct.getAppId());
                    result.put("timeStamp", timestamp);
                    result.put("nonceStr", nonceStr);
                    result.put("package", packages);
                    result.put("paySign", paySign);
                }
            }
        } else {
            result.put("errorCode", "403");
        }

        result.put("env", conf.getBoolean("wx_pay_test_env")?"TEST":"PROD");
        logger.infof("Pay Page: %s", result);
        req.setAttribute("pay", result);
    }

    /**
     * Mark sure the order pay status.
     *
     * @param orderId   the business order number
     * @param req
     * @return
     */
    @At

    public AjaxReturn queryOrderPay(@Param("orderId") String orderId, HttpServletRequest req, HttpServletResponse resp) {
        AjaxReturn ar = Ajax.ok();
        int nonceStr = Utils.buildRandom(10);
        MPSetting mpSet = mpSetService.get();

        SortedMap<String, Object> queryOrderParams = new TreeMap<>();
        queryOrderParams.put("appid", mpSet.getAppId());
        queryOrderParams.put("mch_id", mpSet.getMchId());
        queryOrderParams.put("out_trade_no", orderId);
        queryOrderParams.put("nonce_str", nonceStr);
        queryOrderParams.put("sign_type", "MD5");

        PayRequestHandler reqHandler = new PayRequestHandler(req, resp);
        reqHandler.init(mpSet.getAppId(), mpSet.getAppSecret(), mpSet.getMchKey());

        queryOrderParams.put("sign", reqHandler.createSign(queryOrderParams));
        Request request = Request.create(queryOrderURL, Request.METHOD.POST);
        request.setData(Xmls.mapToXml(queryOrderParams));
        logger.infof("Query order params: %s", Xmls.mapToXml(queryOrderParams));
        Response queryOrderResp = Sender.create(request).send();
        NutMap result = new NutMap("result", "FAIL");
        if (queryOrderResp.isOK()) {
            String queryOrderRsStr = queryOrderResp.getContent("UTF-8");
            logger.infof("Query order response: %s", queryOrderRsStr);
            NutMap queryOrderRs = Xmls.xmlToMap(queryOrderRsStr);
            String resultCode = queryOrderRs.getString("result_code");

            result.put("tradeNo", queryOrderRs.getString("out_trade_no"));
            result.put("errCode", queryOrderRs.getString("err_code"));
            result.put("errCodeDesc", queryOrderRs.getString("err_code_des"));

            if (Strings.equals("SUCCESS", resultCode)) {
                result.put("result", "SUCCESS");
                result.put("state", conf.get(queryOrderRs.getString("trade_state")));
                result.put("stateDesc", queryOrderRs.getString("trade_state_des"));
                if (queryOrderRs.getString("trade_state").equals("SUCCESS")) {
                    result.put("bank", conf.get(queryOrderRs.getString("bank_type"), "未知源"));
                    result.put("totalFee", queryOrderRs.getInt("total_fee"));
                    result.put("cashFee", queryOrderRs.getInt("cash_fee"));
                    result.put("transId", queryOrderRs.getString("transaction_id"));
                    result.put("timeEnd", Utils.parseStr2Date(queryOrderRs.getString("time_end"), "yyyyMMddHHmmss"));
                    result.put("settlementTotalFee", queryOrderRs.getString("settlement_total_fee"));
                }
            } else {
                ar.setOk(false);
                ar.setErrCode("500");
                ar.setMsg(queryOrderRs.getString("err_code_des"));
            }
        } else {
            ar.setOk(false);
            ar.setErrCode("500");
            ar.setMsg("Connect wechat server fail!!!");
        }

        ar.setData(result);
        return ar;
    }

    /**
     * Receive wechat server pay result push and sync our data
     *
     * @param req
     * @return
     */
    @At
    @Ok("raw")
    public String notifyOrderPay(HttpServletRequest req) {

        ByteArrayOutputStream result = null;
        try {
            InputStream input = req.getInputStream();
            result = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length;
            while ((length = input.read(buffer)) != -1) {
                result.write(buffer, 0, length);
            }

            input.close();

        } catch (IOException e) {
            logger.infof("Read wechat server notify stream fail!!!", e);
        }

        if (result != null) {
            logger.infof("Notify response: %s", result);
            String respContent = null;
            try {
                respContent = result.toString("UTF-8");
            } catch (UnsupportedEncodingException e) {
                logger.error("Read wechat server notify message fail!!!", e);
            }

            NutMap notifyResult = Xmls.xmlToMap(respContent);
            logger.infof("Wechat pay notify content: %s", notifyResult);

            String orderNo = notifyResult.getString("out_trade_no");
            String resultCode = notifyResult.getString("result_code");

            if (Strings.equals("SUCCESS", resultCode)) {

                ExecutorThreadPool.execute(() -> {
                    String transactionId = notifyResult.getString("transaction_id");
                    String payTime = notifyResult.getString("time_end");
                    String feeType = notifyResult.getString("fee_type");
                    int cashFee = notifyResult.getInt("cash_fee");
                    String cashFeeType = notifyResult.getString("cash_fee_type");
                    String tradeType = notifyResult.getString("trade_type");
                    String bankType = notifyResult.getString("bank_type");
                    int totalFee = notifyResult.getInt("total_fee");
                    int settlementFee = notifyResult.getInt("settlement_total_fee");
                    String errCode = notifyResult.getString("err_code");
                    String errCodeDesc = notifyResult.getString("err_code_des");

                    OrderInfo orderInfo = new OrderInfo();
                    orderInfo.setOrderId(orderNo);
                    orderInfo.setTransactionId(transactionId);
                    orderInfo.setPayTime(Utils.parseStr2Date(payTime));
                    orderInfo.setFeeType(feeType);
                    orderInfo.setCashFeeType(cashFeeType);
                    orderInfo.setCashFee(cashFee);
                    orderInfo.setTotalFee(totalFee);
                    orderInfo.setBankType(bankType);
                    orderInfo.setTradeType(tradeType);
                    orderInfo.setSettlementTotalFee(settlementFee);
                    orderInfo.setResultCode(resultCode);
                    orderInfo.setDataStatus("2");
                    orderInfo.setErrCode(errCode);
                    orderInfo.setErrCodeDes(errCodeDesc);

                    logger.infof("Notify order info: %s", orderInfo);

                    String orderId = orderInfoService.updateOrderStatus(orderInfo);
                    if (Strings.equals(orderId, orderNo)) {
                        logger.infof("User[%s] had finish wechat pay and transaction id is: %s.",
                                notifyResult.getString("open_id"), transactionId);
                    }
                });
            } else {
                logger.errorf("User[%s] pay goods price fail!!! Error message: %s",
                        notifyResult.getString("open_id"), notifyResult.getString("return_msg"));

                String errCode = notifyResult.getString("err_code");
                String errCodeDesc = notifyResult.getString("err_code_des");
                OrderInfo orderInfo = new OrderInfo();
                orderInfo.setOrderId(orderNo);
                orderInfo.setResultCode(resultCode);
                orderInfo.setErrCode(errCode);
                orderInfo.setErrCodeDes(errCodeDesc);
                String orderId = orderInfoService.updateOrderStatus(orderInfo);
                if (Strings.equals(orderId, orderNo)) {
                    logger.errorf("User[%s]  pay order[%s] was fail!!!",
                            notifyResult.getString("open_id"), orderId);
                }
            }

            String replyContent = PayRequestHandler.setXML("SUCCESS", "OK");
            return replyContent;
        }

        return null;
    }
}
